// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_TORQUE_PARAMETER_DIFFERENCE_H_
#define V8_TORQUE_PARAMETER_DIFFERENCE_H_

#include <vector>

#include "src/torque/types.h"

namespace v8 {
namespace internal {
    namespace torque {

        class ParameterDifference {
        public:
            ParameterDifference(const TypeVector& to, const TypeVector& from)
            {
                DCHECK_EQ(to.size(), from.size());
                for (size_t i = 0; i < to.size(); ++i) {
                    AddParameter(to[i], from[i]);
                }
            }

            // An overload is selected if it is strictly better than all alternatives.
            // This means that it has to be strictly better in at least one parameter,
            // and better or equally good in all others.
            //
            // When comparing a pair of corresponding parameters of two overloads...
            // ... they are considered equally good if:
            //     - They are equal.
            //     - Both require some implicit conversion.
            // ... one is considered better if:
            //     - It is a strict subtype of the other.
            //     - It doesn't require an implicit conversion, while the other does.
            bool StrictlyBetterThan(const ParameterDifference& other) const
            {
                DCHECK_EQ(difference_.size(), other.difference_.size());
                bool better_parameter_found = false;
                for (size_t i = 0; i < difference_.size(); ++i) {
                    base::Optional<const Type*> a = difference_[i];
                    base::Optional<const Type*> b = other.difference_[i];
                    if (a == b) {
                        continue;
                    } else if (a && b && a != b && (*a)->IsSubtypeOf(*b)) {
                        DCHECK(!(*b)->IsSubtypeOf(*a));
                        better_parameter_found = true;
                    } else if (a && !b) {
                        better_parameter_found = true;
                    } else {
                        return false;
                    }
                }
                return better_parameter_found;
            }

        private:
            // Pointwise difference between call arguments and a signature.
            // {base::nullopt} means that an implicit conversion was necessary,
            // otherwise we store the supertype found in the signature.
            std::vector<base::Optional<const Type*>> difference_;

            void AddParameter(const Type* to, const Type* from)
            {
                if (from->IsSubtypeOf(to)) {
                    difference_.push_back(to);
                } else if (IsAssignableFrom(to, from)) {
                    difference_.push_back(base::nullopt);
                } else {
                    UNREACHABLE();
                }
            }
        };

    } // namespace torque
} // namespace internal
} // namespace v8

#endif // V8_TORQUE_PARAMETER_DIFFERENCE_H_
